﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.ServiceModel.Description;
using AmfMessage = WcfFlashRemoting.ServiceModel.Channels.AmfMessage;
using WcfFlashRemoting.ServiceModel.Channels;
using WcfFlashRemoting.AmfObjects;

namespace WcfFlashRemoting.ServiceModel.Dispatcher
{
    class AmfDispatchFormatter : System.ServiceModel.Dispatcher.IDispatchMessageFormatter
    {
        Dictionary<string, Type> knownTypes; 

        OperationDescription opDesc;
        public AmfDispatchFormatter(OperationDescription opDesc)
        {
            this.opDesc = opDesc;
            knownTypes = opDesc.KnownTypes.ToDictionary(type => TypeHelper.GetContractName(type));
        }

        #region IDispatchMessageFormatter Members

        public void DeserializeRequest(System.ServiceModel.Channels.Message message, object[] parameters)
        {
            MessageDescription msgDesc = opDesc.Messages.Where(m => m.Direction == MessageDirection.Input).First();
            if (((message as AmfMessage).AmfBody.Content) == null)
            {
                return;
            }

            if (msgDesc.MessageType == null)
            {
                if (((message as AmfMessage).AmfBody.Content) is object[])
                {
                    object[] data = ((message as AmfMessage).AmfBody.Content) as object[];
                    for (int i = 0; i < data.Length; i++)
                    {
                        parameters[i] = TypeHelper.Instantiate(msgDesc.Body.Parts[i].Type, data[i], knownTypes);
                    }
                }
                else
                {
                    NamedDictionary namedData = ((message as AmfMessage).AmfBody.Content) as NamedDictionary;
                    foreach (KeyValuePair<string, object> kvp in namedData.Properties)
                    {
                        MessagePartDescription part = msgDesc.Body.Parts.First(p => p.Name == kvp.Key);
                        int index = msgDesc.Body.Parts.IndexOf(part);
                        parameters[index] = TypeHelper.Instantiate(part.Type, kvp.Value, knownTypes);
                    }
                }
            }
            else
            {
                parameters[0] = System.Runtime.Serialization.FormatterServices.GetSafeUninitializedObject(msgDesc.MessageType);
                if (((message as AmfMessage).AmfBody.Content) is object[])
                {
                    object[] data = ((message as AmfMessage).AmfBody.Content) as object[];
                    for (int i = 0; i < data.Length; i++)
                    {
                        MessagePartDescription part = msgDesc.Body.Parts[i];
                        Set(parameters[0], part, data[i]);
                    }
                }
                else
                {
                    NamedDictionary namedData = ((message as AmfMessage).AmfBody.Content) as NamedDictionary;
                    foreach (KeyValuePair<string, object> kvp in namedData.Properties)
                    {
                        MessagePartDescription part = msgDesc.Body.Parts.First(p => p.Name == kvp.Key);
                        Set(parameters[0], part, kvp.Value);
                    }
                }

                foreach (AmfHeader header in (message as AmfMessage).AmfHeaders)
                {
                    MessagePartDescription part = msgDesc.Headers.FirstOrDefault(p => p.Name == header.Name);
                    if (part != null)
                    {
                        Set(parameters[0], part, header.Content);
                    }
                    else
                    {
                        if (header.MustUnderstand)
                        {
                            throw new InvalidOperationException("Header '" + header.Name + "' not understood by endpoint.");
                        }
                    }
                }
            }
        }

        private void Set(object instance, MessagePartDescription part, object p)
        {
            object value = TypeHelper.Instantiate(part.Type, p, knownTypes);
            if (part.MemberInfo is System.Reflection.PropertyInfo)
            {
                (part.MemberInfo as System.Reflection.PropertyInfo).SetValue(instance, value, null);
            }
            else
            {
                (part.MemberInfo as System.Reflection.FieldInfo).SetValue(instance, value);
            }
        }

        public System.ServiceModel.Channels.Message SerializeReply(System.ServiceModel.Channels.MessageVersion messageVersion, object[] parameters, object result)
        {
            List<WcfFlashRemoting.AmfObjects.AmfHeader> headers = new List<WcfFlashRemoting.AmfObjects.AmfHeader>();

            WcfFlashRemoting.AmfObjects.AmfBody body = new WcfFlashRemoting.AmfObjects.AmfBody();
            body.Target = null;
            body.Response = null;

            if (result != null)
            {
                if (result.GetType().GetCustomAttributes(typeof(System.ServiceModel.MessageContractAttribute), true).Length > 0)
                {
                    body.Content = TypeHelper.GetBodyNamedDictionaryFor(result);
                    headers.AddRange(TypeHelper.GetHeadersFor(result).Select(kvp => new WcfFlashRemoting.AmfObjects.AmfHeader() { Name = kvp.Key, Content = kvp.Value, MustUnderstand = false }));
                    return new WcfFlashRemoting.ServiceModel.Channels.AmfMessage(messageVersion, headers.ToArray(), body, AmfBaseMessage.GetCurrentAmfVersion);
                }
            }
            
            List<object> content = new List<object>();
            content.Add(result);
            content.AddRange(parameters);
            if (content.Count > 1)
                body.Content = content.ToArray();
            else
                body.Content = content[0];

            return new WcfFlashRemoting.ServiceModel.Channels.AmfMessage(messageVersion, headers.ToArray(), body, AmfBaseMessage.GetCurrentAmfVersion);
        }

        #endregion
    }
}
